/*-
 * Copyright 2012 Guram Dukashvili
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
//---------------------------------------------------------------------------
#include "sys.h"
#include "mutex.h"
//---------------------------------------------------------------------------
static struct mutex runtime;
#if HAVE_SEMAPHORE_H
static sem_t smr_handle_initializer;
#endif
//---------------------------------------------------------------------------
static void mutex_initialize(struct mutex * mtx)
{
#if HAVE_PTHREAD_H
  int err;
  pthread_mutexattr_t attr;
  if( (errno = pthread_mutexattr_init(&attr)) == 0 ){
#if HAVE_SYSCONF
    if( sysconf(_POSIX_THREAD_PRIO_INHERIT) > 0 ){
      if( (errno = pthread_mutexattr_setprotocol(&attr,PTHREAD_PRIO_INHERIT)) == 0 ){
#endif
        errno = pthread_mutex_init(&mtx->handle,&attr);
#if HAVE_SYSCONF
      }
    }
#endif
  }
  err = errno;
  pthread_mutexattr_destroy(&attr);
  errno = err;
#elif __WIN32__
  errno = 0;
  mtx->handle = CreateMutexA(NULL,FALSE,NULL);
  if( mtx->handle == NULL ){
    errno = ENOMEM;
  }
#else
  errno = ENOSYS;
  perror(NULL);
#if COMPILE_RUNTIME_CHECKS
  fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
  abort();
#endif
#if HAVE_SEMAPHORE_H
  memset(&smr_handle_initializer,0,sizeof(smr_handle_initializer));
#endif
}
//---------------------------------------------------------------------------
void mutex_init_runtime(void)
{
  mutex_init(&runtime);
  mutex_initialize(&runtime);
  if( errno != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
}
//---------------------------------------------------------------------------
void mutex_destroy_runtime(void)
{
  mutex_destroy(&runtime);
  if( errno != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
}
//---------------------------------------------------------------------------
#if HAVE_PTHREAD_H
static const pthread_mutex_t null = PTHREAD_MUTEX_INITIALIZER;
#endif
//---------------------------------------------------------------------------
#if HAVE_PTHREAD_H
static pthread_mutex_t * mtx_handle(struct mutex * mtx)
#elif __WIN32__
static HANDLE mtx_handle(struct mutex * mtx)
#endif
{
#if HAVE_PTHREAD_H
  if( memcmp(&mtx->handle,&null,sizeof(mtx->handle)) == 0 ){
#else
  if( mtx->handle == NULL ){
#endif
    mutex_acquire(&runtime);
#if HAVE_PTHREAD_H
    if( memcmp(&mtx->handle,&null,sizeof(mtx->handle)) == 0 )
#else
    if( mtx->handle == NULL )
#endif
      mutex_initialize(mtx);
    mutex_release(&runtime);
  }
#if __WIN32__
  return mtx->handle;
#else
  return &mtx->handle;
#endif
}
//---------------------------------------------------------------------------
struct mutex * mutex_new(void)
{
  struct mutex * mtx = salloc(sizeof(struct mutex),NULL);
  if( mtx != NULL )
    mutex_init(mtx);
  return mtx;
}
//---------------------------------------------------------------------------
void mutex_init(struct mutex * mtx)
{
#if __WIN32__
  mtx->handle = NULL;
#elif HAVE_PTHREAD_T
  mtx->handle = PTHREAD_MUTEX_INITIALIZER;
#else
  errno = ENOSYS;
  perror(NULL);
#if COMPILE_RUNTIME_CHECKS
  fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
  abort();
#endif
#ifndef NDEBUG
  mtx->acquired = false;
#endif
}
//---------------------------------------------------------------------------
void mutex_destroy(struct mutex * mtx)
{
#if HAVE_SEMAPHORE_H
  if( memcmp(&mtx->handle,&null,sizeof(mtx->handle)) != 0 ){
    if( (errno = pthread_mutex_destroy(&mtx->handle)) != 0 ){
      perror(NULL);
#if COMPILE_RUNTIME_CHECKS
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
      abort();
    }
    mtx->handle = null;
  }
#elif __WIN32__
  if( mtx->handle != NULL ){
    if( CloseHandle(mtx->handle) == 0 ){
#if COMPILE_RUNTIME_CHECKS
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
      abort();
    }
    mtx->handle = NULL;
  }
#endif
}
//---------------------------------------------------------------------------
void mutex_delete(struct mutex * mtx)
{
  mutex_destroy(mtx);
  sfree(mtx);
}
//---------------------------------------------------------------------------
void mutex_release(struct mutex * mtx)
{
#if HAVE_SEMAPHORE_H
  errno = pthread_mutex_unlock(&mtx->handle);
#elif __WIN32__
  errno = 0;
  if( ReleaseMutex(mtx->handle) == 0 ){
    DWORD err = GetLastError();
    errno = err == ERROR_NOT_OWNER ? EPERM : EINVAL;
  }
#endif
  if( errno != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#ifndef NDEBUG
  mtx->acquired = false;
#endif
}
//---------------------------------------------------------------------------
void mutex_acquire(struct mutex * mtx)
{
#if HAVE_PTHREAD_H
  errno = pthread_mutex_lock(mtx_handle(mtx));
#elif __WIN32__
  DWORD r;
  errno = 0;
  r = WaitForSingleObjectEx(mtx_handle(mtx),INFINITE,FALSE);
  if( r != WAIT_OBJECT_0 && r != WAIT_ABANDONED ){
    DWORD e = GetLastError();
    errno = e == ERROR_INVALID_HANDLE ? EINVAL : ENOLCK;
  }
#endif
  if( errno != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#ifndef NDEBUG
  mtx->acquired = true;
#endif
}
//---------------------------------------------------------------------------
bool mutex_try_acquire(struct mutex * mtx)
{
#if HAVE_PTHREAD_H
  errno = pthread_mutex_trylock(mtx_handle(mtx));
  if( errno != 0 && errno != EBUSY ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#ifndef NDEBUG
  mtx->acquired = errno == 0;
#endif
  return errno == 0;
#elif __WIN32__
  DWORD r;
  errno = 0;
  r = WaitForSingleObjectEx(mtx_handle(mtx),0,FALSE);
  if( r == WAIT_FAILED )
    errno = EINVAL;
  if( errno != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#ifndef NDEBUG
  mtx->acquired = r == WAIT_OBJECT_0;
#endif
  return r == WAIT_OBJECT_0/* || r == WAIT_ABANDONED*/;
#endif
}
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////////////////
//---------------------------------------------------------------------------
void semaphore_initialize(struct semaphore * smr)
{
#if HAVE_SEMAPHORE_H
  smr->handle = smr_handle_initializer;
  if( sem_init(&smr->handle,0,smr->initial_value) != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#elif __WIN32__
  errno = 0;
  smr->handle = CreateSemaphoreA(NULL,smr->initial_value,~(ULONG) 0 >> 1,NULL);
  if( smr->handle == NULL )
    errno = ENOMEM;
  if( errno != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#endif
}
//---------------------------------------------------------------------------
#if HAVE_SEMAPHORE_H
static sem_t * smr_handle(struct semaphore * smr)
#elif __WIN32__
static HANDLE smr_handle(struct semaphore * smr)
#endif
{
#if HAVE_SEMAPHORE_H
  if( memcmp(&smr->handle,&smr_handle_initializer,sizeof(smr_handle_initializer)) == 0 ){
    mutex_acquire(&runtime);
    if( memcmp(&smr->handle,&smr_handle_initializer,sizeof(smr_handle_initializer)) == 0 )
      semaphore_initialize(smr);
    mutex_release(&runtime);
  }
  return &smr->handle;
#elif __WIN32__
  if( smr->handle == NULL ){
    mutex_acquire(&runtime);
    if( smr->handle == NULL )
      semaphore_initialize(smr);
    mutex_release(&runtime);
  }
  return smr->handle;
#endif
}
//---------------------------------------------------------------------------
struct semaphore * semaphore_new(void)
{
  struct semaphore * smr = salloc(sizeof(struct semaphore),NULL);
  if( smr != NULL )
    semaphore_init(smr);
  return smr;
}
//---------------------------------------------------------------------------
void semaphore_init(struct semaphore * smr)
{
  semaphore_init_v(smr,0);
}
//---------------------------------------------------------------------------
void semaphore_init_v(struct semaphore * smr,uint32_t initial_value)
{
#if HAVE_SEMAPHORE_H
  smr->handle = smr_handle_initializer;
#elif __WIN32__
  smr->handle = NULL;
#endif
  smr->initial_value = initial_value;
}
//---------------------------------------------------------------------------
void semaphore_destroy(struct semaphore * smr)
{
#if HAVE_SEMAPHORE_H
  if( memcmp(&smr->handle,&smr_handle_initializer,sizeof(smr_handle_initializer)) != 0 ){
    if( sem_destroy(&smr->handle) != 0 ){
      perror(NULL);
#if COMPILE_RUNTIME_CHECKS
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
      abort();
    }
    smr->handle = smr_handle_initializer;
#elif __WIN32__
  if( smr->handle != NULL ){
    errno = 0;
    if( CloseHandle(smr->handle) == 0 )
      errno = EINVAL;
    if( errno != 0 ){
      perror(NULL);
#if COMPILE_RUNTIME_CHECKS
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
      abort();
    }
    smr->handle = NULL;
#endif
  }
}
//---------------------------------------------------------------------------
void semaphore_delete(struct semaphore * smr)
{
  semaphore_destroy(smr);
  sfree(smr);
}
//---------------------------------------------------------------------------
void semaphore_post(struct semaphore * smr)
{
#if HAVE_SEMAPHORE_H
  if( sem_post(smr_handle(smr)) != 0 ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#elif __WIN32__
  if( ReleaseSemaphore(smr_handle(smr),1,NULL) == 0 ){
    errno = GetLastError() == ERROR_NOT_OWNER ? EPERM : EINVAL;
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#endif
}
//---------------------------------------------------------------------------
void semaphore_post_v(struct semaphore * smr,uintptr_t count)
{
#if HAVE_SEMAPHORE_H
  while( count > 0 ){
    if( sem_post(smr_handle(smr)) != 0 ){
      perror(NULL);
#if COMPILE_RUNTIME_CHECKS
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
      abort();
    }
    count--;
  }
#elif __WIN32__
#if COMPILE_RUNTIME_CHECKS
  if( count > ((~(LONG) 0) & ((LONG) 1 << (sizeof(LONG) * 8 - 1))) ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  if( count > 0 && ReleaseSemaphore(smr_handle(smr),(LONG) count,NULL) == 0 ){
    errno = GetLastError() == ERROR_NOT_OWNER ? EPERM : EINVAL;
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#endif
}
//---------------------------------------------------------------------------
void semaphore_wait(struct semaphore * smr)
{
#if HAVE_SEMAPHORE_H
  while( sem_wait(smr_handle(smr)) != 0 ){
    if( errno != EINTR ){
      perror(NULL);
#if COMPILE_RUNTIME_CHECKS
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
      abort();
    }
  }
#elif __WIN32__
  DWORD r = WaitForSingleObjectEx(smr_handle(smr),INFINITE,FALSE);
  if( r != WAIT_OBJECT_0 && r != WAIT_ABANDONED ){
    errno = GetLastError() == ERROR_NOT_OWNER ? EPERM : EINVAL;
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
#endif
}
//---------------------------------------------------------------------------
bool semaphore_timed_wait(struct semaphore * smr,uint64_t timeout)
{
#if HAVE_SEMAPHORE_H
#if HAVE_SEM_TIMEDWAIT
  int r;
  struct timespec t;
#if HAVE_CLOCK_GETTIME
  clock_gettime(CLOCK_REALTIME,&t);
  timeout = ((uint64_t) 1000000u * t.tv_sec + timeout) * 1000u + t.tv_nsec;
#else
  timeout = (timeout + get_time()) * 1000u;
#endif
  t.tv_sec = timeout / (1000000u * 1000u);
  t.tv_nsec = timeout % (1000000u * 1000u);

  while( (r = sem_timedwait(smr_handle(smr),&t)) != 0 && errno == EINTR );
  if( r != 0 && errno != ETIMEDOUT ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
  return r == 0;
#else
  bool r;
  uint64_t t = gettimeofday();
  for(;;){
    r = semaphore_try_wait(smr);
    if( r ) break;
    if( gettimeofday() - t >= timeout ) break;
    portable_sleep1();
  }
  return r;
#endif
#elif __WIN32__
  DWORD r;
  uint64_t t = timeout / 1000u + (timeout > 0 && timeout < 1000u);
  r = WaitForSingleObjectEx(smr_handle(smr),t > ~(DWORD) 0 - 1 ? ~(DWORD) 0 - 1 : (DWORD) t,FALSE);
  if( r == WAIT_FAILED ){
    errno = EINVAL;
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
  errno = r == WAIT_TIMEOUT ? ETIMEDOUT : 0;
  return r != WAIT_TIMEOUT;
#endif
}
//---------------------------------------------------------------------------
bool semaphore_try_wait(struct semaphore * smr)
{
#if HAVE_SEMAPHORE_H
  int r = sem_trywait(smr_handle(smr));
  if( r != 0 && errno != EAGAIN ){
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
  return r == 0;
#elif __WIN32__
  DWORD r = WaitForSingleObjectEx(smr_handle(smr),0,FALSE);
  if( r == WAIT_FAILED ){
    errno = GetLastError() == ERROR_NOT_OWNER ? EPERM : EINVAL;
    perror(NULL);
#if COMPILE_RUNTIME_CHECKS
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
#endif
    abort();
  }
  return r == WAIT_OBJECT_0 || r == WAIT_ABANDONED;
#endif
}
//---------------------------------------------------------------------------
struct cond_var * cond_var_new(void)
{
  struct cond_var * var = salloc(sizeof(struct cond_var),NULL);
  if( var != NULL )
    cond_var_init(var);
  return var;
}
//---------------------------------------------------------------------------
void cond_var_init(struct cond_var * var)
{
  cond_var_init_v(var,false);
}
//---------------------------------------------------------------------------
void cond_var_init_v(struct cond_var * var,bool v)
{
  mutex_init(&var->lock);
  semaphore_init(&var->wait);
  var->waiters = 0;
  var->v = v;
}
//---------------------------------------------------------------------------
void cond_var_destroy(struct cond_var * var)
{
#if COMPILE_RUNTIME_CHECKS
  if( var->waiters != 0 ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  semaphore_destroy(&var->wait);
  mutex_destroy(&var->lock);
}
//---------------------------------------------------------------------------
void cond_var_delete(struct cond_var * var)
{
  cond_var_destroy(var);
  sfree(var);
}
//---------------------------------------------------------------------------
void cond_var_set(struct cond_var * var,bool v)
{
  mutex_acquire(&var->lock);
  var->v = v;
  semaphore_post_v(&var->wait,var->waiters);
  mutex_release(&var->lock);
}
//---------------------------------------------------------------------------
static void wait_equ_cycle(struct cond_var * var,bool v)
{
  mutex_acquire(&var->lock);
  for(;;){
    bool w = var->v != v;
    if( w ) break;
    var->waiters++;
    mutex_release(&var->lock);
    semaphore_wait(&var->wait);
    mutex_acquire(&var->lock);
    var->waiters--;
  }
}
//---------------------------------------------------------------------------
void cond_var_wait_equ(struct cond_var * var,bool v)
{
  wait_equ_cycle(var,v);
  mutex_release(&var->lock);
}
//---------------------------------------------------------------------------
static bool wait_equ_timeout_cycle(struct cond_var * var,bool v,uint64_t timeout)
{
  bool w;
  uint64_t t = 0;

  mutex_acquire(&var->lock);
  for(;;){
    w = var->v != v;
    if( w || timeout == 0 ) break;
    var->waiters++;
    mutex_release(&var->lock);
    t = get_time();
    semaphore_timed_wait(&var->wait,timeout);
    t = get_time() - t;
    timeout = t >= timeout ? 0 : timeout - t;
    mutex_acquire(&var->lock);
    var->waiters--;
  }
  return w;
}
//---------------------------------------------------------------------------
bool cond_var_wait_equ_timeout(struct cond_var * var,bool v,uint64_t timeout)
{
  bool w = wait_equ_timeout_cycle(var,v,timeout);
  mutex_release(&var->lock);
  return w;
}
//---------------------------------------------------------------------------
